#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import os
import platform
import signal

import psutil

from common.cleaner import clear
from common.common import execute_cmd
from common.const import IPConstant, CMDResult
from common.parse_parafile import get_env_variable
from oracle import logger
from oracle.common.common import login_database, enter_password, exec_sql_cmd, parse_html_result
from oracle.common.constants import Platform, PexpectResult
from oracle.common.lun_common import check_lun_metro_pair_status
from oracle.common.user_env_common import get_oracle_user_env_by_linux, get_asm_user_env_by_linux
from oracle.common.windows_common import execute_windows_cmd
from oracle.services.storage.storage_v6_manager import StorageV6Manager


def login_oracle_database_restore(pid, db_instance_name, db_login_params, install_user_name, timeout=600):
    """
    交互式的方式登录oracle数据库
    :param pid: pid
    :param db_instance_name: 数据库实例名
    :param install_user_name: 数据库安装用户名
    :param timeout: 超时时间
    :param db_login_params: 登录数据库所用参数
    :return: 交互式的句柄
    """
    auth_pwd = db_login_params.get("auth_pwd", "")
    auth_key = db_login_params.get("auth_key", "")
    db_pass_word = db_login_params.get("db_pass_word", "")
    result, child = login_database(pid, db_instance_name, install_user_name, timeout)
    if not result:
        logger.error('Login oracle database error.')
        return False, child
    if not db_pass_word:
        db_pass_word = get_env_variable(auth_pwd)
    result, child = enter_password(child, get_env_variable(auth_key), db_pass_word)
    clear(db_pass_word)
    if not result:
        logger.error('Invalid username or password.')
        return False, child

    return True, child


def exec_oracle_sql_cmd(params, cmd, expect_result, timeout=300):
    logger.info(f"exec_oracle_sql_cmd, cmd {cmd}")
    pid = params.get("pid")
    instance_name = params.get("instance_name")
    db_install_user = params.get("db_install_user")
    result, child = login_oracle_database_restore(pid, instance_name, params, db_install_user,
                                                  timeout=timeout)
    if not result:
        return False
    try:
        return_code, std_out = exec_sql_cmd(child, cmd, expect_result)
        if return_code:
            return True
        logger.error(f"Failed to execute cmd: {cmd}, std_out: {std_out}.")
        return False
    finally:
        if platform.system().lower() == Platform.WINDOWS:
            child.kill(signal.SIGTERM)
        else:
            child.close()


def get_register_ip(host_ip):
    """
    获取当前节点注册ip
    :return:
    """
    cur_host_ips = set(extract_ip())
    logger.debug(f"ip cur_host_ips: {cur_host_ips}")
    remote_ips = set(host_ip)
    result_set = cur_host_ips & remote_ips
    logger.debug(f"ip result_set: {result_set}")
    if not result_set or len(result_set) != 1:
        logger.error(f"Get register ip failed, result_set count: {len(result_set)}.")
        return ""
    return result_set.pop()


def extract_ip():
    """
    获取当前主机所有ip
    :return:
    """
    logger.info(f"Start getting all local ips ...")
    local_ips = []
    ip_dict = {}
    try:
        ip_dict = psutil.net_if_addrs()
    except Exception as err:
        logger.error(f"Get ip address err: {err}.")
        return local_ips
    for _, info_list in ip_dict.items():
        for i in info_list:
            if i[0] == 2 and i[1] != IPConstant.LOCAL_HOST:
                local_ips.append(i[1])
    logger.info(f"Get all local ips success.")
    return local_ips


def check_hyper_metro_storage_status(lun_infos, device_manager):
    # 检查双活pair状态
    ret, hyper_metro_pairs = StorageV6Manager.query_lun_hyper_metro_pair(device_manager)
    if not ret:
        logger.error(f"check_metro_pair_storage_status query_lun_hyper_metro_pair failed")
        return False
    return check_lun_metro_pair_status(lun_infos, hyper_metro_pairs)


def shutdown_abort(login_params):
    logger.info(f"_shutdown_abort")
    cmd = f"shutdown abort;"
    return exec_oracle_sql_cmd(login_params, cmd, PexpectResult.SHUTDOWN, 60)


def check_inst_status(pid, db_instance_name, install_user_name):
    logger.info("Start check_inst_status")
    cmd = "SELECT open_mode FROM v$database;"
    db_login_params = {
        'auth_pwd': f"job_targetObject_auth_authPwd_{pid}",
        'auth_key': f"job_targetObject_auth_authKey_{pid}"
    }
    result, child = login_oracle_database_restore(pid, db_instance_name, db_login_params, install_user_name,
                                                  timeout=600)
    if not result:
        return False
    try:
        return_code, std_out = exec_sql_cmd(child, cmd)
        if return_code:
            results = parse_html_result(std_out)
            inst_status = results[0].get('OPEN_MODE')
            return inst_status == 'READ WRITE'
        logger.error(f"Failed to execute cmd: {cmd}, pid: {pid}.")
        return False
    finally:
        if platform.system().lower() == "windows":
            child.kill(signal.SIGTERM)
        else:
            child.close()


def modify_asm_spfile_path(pid, asm_install_user_name, asm_spfile_path):
    if not asm_install_user_name:
        asm_install_user_name = 'grid'
    oracle_user_env = get_oracle_user_env_by_linux()
    cmd = f"su - {asm_install_user_name} -c '{oracle_user_env}srvctl modify asm -p {asm_spfile_path}'"
    logger.info(f"modify_asm_spfile_path, cmd {cmd}")
    return_code, std_out, std_err = execute_cmd(cmd)
    if return_code != CMDResult.SUCCESS:
        logger.error(f"modify_asm_spfile_path failed, std_out {std_out} std_err {std_err}")
        raise Exception(f"modify_asm_spfile_path failed, pid {pid}, std_err {std_err}")
    logger.info(f"modify_asm_spfile_path success, pid {pid}")


def check_restore_path_exits_for_pdb(pid, dir_path, asm_install_user_name, asm_instance, oracle_home):
    if dir_path[0].isalpha() or dir_path[0] == "/":
        return os.path.exists(dir_path)
    elif dir_path[0] == "+":
        return check_disk_group_exits(pid, dir_path, asm_install_user_name, asm_instance, oracle_home)
    return False


def check_disk_group_exits(pid, dg_name, asm_install_user_name, asm_instance, oracle_home):
    if not dg_name or not asm_instance:
        logger.error(f"Param is invalid dg name or asm instance is none, pid:{pid}")
        return False
    dg_name = dg_name.upper()
    dg_name_parent = os.path.split(dg_name)[0]
    dg_name_child = os.path.split(dg_name)[1] + '/'
    if dg_name_child[0] == "+":
        dg_name_child = dg_name_child[1:]
    if platform.system().lower() == "windows":
        dg_name = dg_name_parent.upper()
        os.environ["ORACLE_SID"] = asm_instance
        os.environ["ORACLE_HOME"] = oracle_home
        os.environ["NLS_LANG"] = "American_America.US7ASCII"
        cmd = f"cmd /c asmcmd ls {dg_name}"
        ret, std_out = execute_windows_cmd(pid, cmd.split(), "")
        if not std_out:
            logger.info(f"Check windows dist group not exist, pid:{pid}.")
            return False
        folders = std_out.split("\r\n")
        folders = [folder.upper() for folder in folders if folder != '']
    else:
        oracle_user_env = get_asm_user_env_by_linux(asm_install_user_name, asm_instance)
        cmd = f"su - {asm_install_user_name} -c '{oracle_user_env} asmcmd ls {dg_name_parent}'"
        logger.info(f"check linux disk group exits, cmd {cmd}")
        return_code, std_out, std_err = execute_cmd(cmd)
        if not std_out:
            logger.info(f"Check linux dist group not exist, pid:{pid}, std_out={std_out}, std_err={std_err}.")
            return False
        logger.info(f"std_out={std_out}")
        folders = std_out.split("\n")
        folders = [folder for folder in folders if folder != '']
        folders = [folder.upper() for folder in folders]
    logger.info(f"folders={folders}")
    return True if dg_name_child in folders else False


def get_pdb_name_set(pdb_list):
    pdb_name_set = set()
    for pdb in pdb_list:
        if pdb.get('RESTRICTED') == "YES":
            pdb_name_set.add(pdb.get('CON_NAME'))
    return pdb_name_set


def get_violation_message_set(violations):
    message_set = set()
    for violation in violations:
        if "quota unlimited" in violation.get('MESSAGE'):
            message_set.add(violation.get('MESSAGE'))
    return message_set